describe('refreshClusters', function () {
    /////////////////////////////
    // SETUP FOR EACH TEST
    /////////////////////////////
    var clock, div, map, group;

    beforeEach(function () {
        clock = sinon.useFakeTimers();

        div = document.createElement('div');
        div.style.width = '200px';
        div.style.height = '200px';
        document.body.appendChild(div);

        map = L.map(div, {maxZoom: 18, trackResize: false});

        // Corresponds to zoom level 8 for the above div dimensions.
        map.fitBounds(new L.LatLngBounds([
            [1, 1],
            [2, 2]
        ]));
    });

    afterEach(function () {
        if (group instanceof L.MarkerClusterGroup) {
            group.clearLayers();
            map.removeLayer(group);
        }

        map.remove();
        div.remove()
        clock.restore();

        clock = div = map = group = null;
    });

    function getClusterAtZoom(marker, zoom) {
        var parent = marker.__parent;

        while (parent && parent._zoom !== zoom) {
            parent = parent.__parent;
        }

        return parent;
    }

    function setMapView() {
        // Now look at the markers to force cluster icons drawing.
        // Corresponds to zoom level 8 for the above div dimensions.
        map.fitBounds(new L.LatLngBounds([
            [1, 1],
            [2, 2]
        ]));
    }

    /////////////////////////////
    // TESTS
    /////////////////////////////
    it('flags all non-visible parent clusters of a given marker', function () {

        group = L.markerClusterGroup().addTo(map);

        var marker1 = L.marker([1.5, 1.5]).addTo(group),
            marker2 = L.marker([1.5, 1.5]).addTo(group); // Needed to force a cluster.

        setMapView();

        var marker1cluster10 = getClusterAtZoom(marker1, 10),
            marker1cluster2 = getClusterAtZoom(marker1, 2),
            marker1cluster5 = getClusterAtZoom(marker1, 5);

        // First go to some zoom levels so that Leaflet initializes clusters icons.
        expect(marker1cluster10._iconNeedsUpdate).to.be.ok();
        map.setZoom(10, {animate: false});
        expect(marker1cluster10._iconNeedsUpdate).to.not.be.ok();

        expect(marker1cluster2._iconNeedsUpdate).to.be.ok();
        map.setZoom(2, {animate: false});
        expect(marker1cluster2._iconNeedsUpdate).to.not.be.ok();

        // Finish on an intermediate zoom level.
        expect(marker1cluster5._iconNeedsUpdate).to.be.ok();
        map.setZoom(5, {animate: false});
        expect(marker1cluster5._iconNeedsUpdate).to.not.be.ok();

        // Run any animation.
        clock.tick(1000);

        // Then request clusters refresh.
        // No need to actually modify the marker.
        group.refreshClusters(marker1);

        // Now check that non-visible clusters are flagged as "dirty".
        expect(marker1cluster10._iconNeedsUpdate).to.be.ok();
        expect(marker1cluster2._iconNeedsUpdate).to.be.ok();

        // Also check that visible clusters are "un-flagged" since they should be re-drawn.
        expect(marker1cluster5._iconNeedsUpdate).to.not.be.ok();

    });

    it('re-draws visible clusters', function () {

        group = L.markerClusterGroup({
            iconCreateFunction: function (cluster) {
                var markers = cluster.getAllChildMarkers();

                for (var i in markers) {
                    if (markers[i].changed) {
                        return new L.DivIcon({
                            className: "changed"
                        });
                    }
                }
                return new L.DivIcon({
                    className: "original"
                });
            }
        }).addTo(map);

        var marker1 = L.marker([1.5, 1.5]).addTo(group),
            marker2 = L.marker([1.5, 1.5]).addTo(group); // Needed to force a cluster.

        setMapView();

        var marker1cluster9 = getClusterAtZoom(marker1, 9);

        // First go to some zoom levels so that Leaflet initializes clusters icons.
        expect(marker1cluster9._iconNeedsUpdate).to.be.ok();
        map.setZoom(9, {animate: false});
        expect(marker1cluster9._iconNeedsUpdate).to.not.be.ok();

        expect(marker1cluster9._icon.className).to.contain("original");
        expect(marker1cluster9._icon.className).to.not.contain("changed");

        // Run any animation.
        clock.tick(1000);

        // Alter the marker.
        marker1.changed = true;

        // Then request clusters refresh.
        group.refreshClusters(marker1);

        // Now check that visible clusters icon is re-drawn.
        expect(marker1cluster9._icon.className).to.contain("changed");
        expect(marker1cluster9._icon.className).to.not.contain("original");

    });

    // Shared code for the 2 below tests
    function iconCreateFunction(cluster) {
        var markers = cluster.getAllChildMarkers();

        for (var i in markers) {
            if (markers[i].changed) {
                return new L.DivIcon({
                    className: "changed"
                });
            }
        }
        return new L.DivIcon({
            className: "original"
        });
    }

    it('re-draws markers in singleMarkerMode', function () {

        group = L.markerClusterGroup({
            singleMarkerMode: true,
            iconCreateFunction: iconCreateFunction
        }).addTo(map);

        var marker1 = L.marker([1.5, 1.5]).addTo(group);

        setMapView();

        expect(marker1._icon.className).to.contain("original");

        // Alter the marker.
        marker1.changed = true;

        // Then request clusters refresh.
        group.refreshClusters(marker1);

        expect(marker1._icon.className).to.contain("changed");
        expect(marker1._icon.className).to.not.contain("original");

    });

    it('does not modify markers that do not belong to the current group (in singleMarkerMode)', function () {

        group = L.markerClusterGroup({
            singleMarkerMode: true,
            iconCreateFunction: iconCreateFunction
        }).addTo(map);

        var marker1 = L.marker([1.5, 1.5]).addTo(group),
            marker2 = L.marker([1.5, 1.5], {
                icon: iconCreateFunction({
                    getAllChildMarkers: function () {
                        return marker2;
                    }
                })
            }).addTo(map);

        setMapView();

        expect(marker1._icon.className).to.contain("original");
        expect(marker2._icon.className).to.contain("original");

        // Alter the markers.
        marker1.changed = true;
        marker2.changed = true;

        // Then request clusters refresh.
        group.refreshClusters([marker1, marker2]);

        expect(marker1._icon.className).to.contain("changed");
        expect(marker1._icon.className).to.not.contain("original");

        expect(marker2._icon.className).to.contain("original");
        expect(marker2._icon.className).to.not.contain("changed");

    });


    // Shared code for below tests.
    var marker1 = L.marker([1.5, 1.5]),
        marker2 = L.marker([1.5, 1.5]), // Needed to force a cluster.
        marker3 = L.marker([1.1, 1.1]),
        marker4 = L.marker([1.1, 1.1]), // Needed to force a cluster.
        marker5 = L.marker([1.9, 1.9]),
        marker6 = L.marker([1.9, 1.9]), // Needed to force a cluster.
        marker1cluster8,
        marker1cluster3,
        marker1cluster5,
        marker3cluster8,
        marker3cluster3,
        marker3cluster5,
        marker5cluster8,
        marker5cluster3,
        marker5cluster5;

    function init3clusterBranches() {

        group = L.markerClusterGroup({
            maxClusterRadius: 2 // Make sure we keep distinct clusters.
        }).addTo(map);

        // Populate Marker Cluster Group.
        marker1.addTo(group);
        marker2.addTo(group);
        marker3.addTo(group);
        marker4.addTo(group);
        marker5.addTo(group);
        marker6.addTo(group);

        setMapView();

        marker1cluster8 = getClusterAtZoom(marker1, 8);
        marker1cluster3 = getClusterAtZoom(marker1, 3);
        marker1cluster5 = getClusterAtZoom(marker1, 5);
        marker3cluster8 = getClusterAtZoom(marker3, 8);
        marker3cluster3 = getClusterAtZoom(marker3, 3);
        marker3cluster5 = getClusterAtZoom(marker3, 5);
        marker5cluster8 = getClusterAtZoom(marker5, 8);
        marker5cluster3 = getClusterAtZoom(marker5, 3);
        marker5cluster5 = getClusterAtZoom(marker5, 5);

        // Make sure we have 3 distinct clusters up to zoom level Z (let's choose Z = 3)
        expect(marker1cluster3._childCount).to.equal(2);
        expect(marker3cluster3._childCount).to.equal(2);
        expect(marker5cluster3._childCount).to.equal(2);

        // First go to some zoom levels so that Leaflet initializes clusters icons.
        expect(marker1cluster8._iconNeedsUpdate).to.not.be.ok();
        expect(marker3cluster8._iconNeedsUpdate).to.not.be.ok();
        expect(marker5cluster8._iconNeedsUpdate).to.not.be.ok();

        expect(marker1cluster3._iconNeedsUpdate).to.be.ok();
        expect(marker3cluster3._iconNeedsUpdate).to.be.ok();
        expect(marker5cluster3._iconNeedsUpdate).to.be.ok();
        map.setZoom(3, {animate: false});
        expect(marker1cluster3._iconNeedsUpdate).to.not.be.ok();
        expect(marker3cluster3._iconNeedsUpdate).to.not.be.ok();
        expect(marker5cluster3._iconNeedsUpdate).to.not.be.ok();

        // Finish on an intermediate zoom level.
        expect(marker1cluster5._iconNeedsUpdate).to.be.ok();
        expect(marker3cluster5._iconNeedsUpdate).to.be.ok();
        expect(marker5cluster5._iconNeedsUpdate).to.be.ok();
        map.setZoom(5, {animate: false});
        expect(marker1cluster5._iconNeedsUpdate).to.not.be.ok();
        expect(marker3cluster5._iconNeedsUpdate).to.not.be.ok();
        expect(marker5cluster5._iconNeedsUpdate).to.not.be.ok();

        // Run any animation.
        clock.tick(1000);

        // Ready to refresh clusters with method of choice and assess result.
    }

    it('does not flag clusters of other markers', function () {

        init3clusterBranches();

        // Then request clusters refresh.
        // No need to actually modify the marker.
        group.refreshClusters(marker1);

        // Now check that non-visible clusters are flagged as "dirty".
        expect(marker1cluster8._iconNeedsUpdate).to.be.ok();
        expect(marker1cluster3._iconNeedsUpdate).to.be.ok();

        // Finally check that non-involved clusters are not "dirty".
        expect(marker3cluster8._iconNeedsUpdate).to.not.be.ok();
        expect(marker3cluster3._iconNeedsUpdate).to.not.be.ok();

        expect(marker5cluster8._iconNeedsUpdate).to.not.be.ok();
        expect(marker5cluster3._iconNeedsUpdate).to.not.be.ok();

    });

    it('processes itself when no argument is passed', function () {

        init3clusterBranches();

        // Then request clusters refresh.
        // No need to actually modify the marker.
        group.refreshClusters();

        // Now check that non-visible clusters are flagged as "dirty".
        expect(marker1cluster8._iconNeedsUpdate).to.be.ok();
        expect(marker1cluster3._iconNeedsUpdate).to.be.ok();

        expect(marker3cluster8._iconNeedsUpdate).to.be.ok();
        expect(marker3cluster3._iconNeedsUpdate).to.be.ok();

        expect(marker5cluster8._iconNeedsUpdate).to.be.ok();
        expect(marker5cluster3._iconNeedsUpdate).to.be.ok();

    });

    it('accepts an array of markers', function () {

        init3clusterBranches();

        // Then request clusters refresh.
        // No need to actually modify the markers.
        group.refreshClusters([marker1, marker5]);
        // Clusters of marker3 and 4 shall not be flagged.

        // Now check that non-visible clusters are flagged as "dirty".
        expect(marker1cluster8._iconNeedsUpdate).to.be.ok();
        expect(marker1cluster3._iconNeedsUpdate).to.be.ok();

        expect(marker5cluster8._iconNeedsUpdate).to.be.ok();
        expect(marker5cluster3._iconNeedsUpdate).to.be.ok();

        // Clusters of marker3 and 4 shall not be flagged.
        expect(marker3cluster8._iconNeedsUpdate).to.not.be.ok();
        expect(marker3cluster3._iconNeedsUpdate).to.not.be.ok();

    });

    it('accepts a mapping of markers', function () {

        init3clusterBranches();

        // Then request clusters refresh.
        // No need to actually modify the markers.
        group.refreshClusters({
            id1: marker1,
            id2: marker5
        }); // Clusters of marker3 and 4 shall not be flagged.

        // Now check that non-visible clusters are flagged as "dirty".
        expect(marker1cluster8._iconNeedsUpdate).to.be.ok();
        expect(marker1cluster3._iconNeedsUpdate).to.be.ok();

        expect(marker5cluster8._iconNeedsUpdate).to.be.ok();
        expect(marker5cluster3._iconNeedsUpdate).to.be.ok();

        // Clusters of marker3 and 4 shall not be flagged.
        expect(marker3cluster8._iconNeedsUpdate).to.not.be.ok();
        expect(marker3cluster3._iconNeedsUpdate).to.not.be.ok();

    });

    it('accepts an L.LayerGroup', function () {

        init3clusterBranches();

        // Then request clusters refresh.
        // No need to actually modify the markers.
        var layerGroup = new L.LayerGroup([marker1, marker5]);
        group.refreshClusters(layerGroup);
        // Clusters of marker3 and 4 shall not be flagged.

        // Now check that non-visible clusters are flagged as "dirty".
        expect(marker1cluster8._iconNeedsUpdate).to.be.ok();
        expect(marker1cluster3._iconNeedsUpdate).to.be.ok();

        expect(marker5cluster8._iconNeedsUpdate).to.be.ok();
        expect(marker5cluster3._iconNeedsUpdate).to.be.ok();

        // Clusters of marker3 and 4 shall not be flagged.
        expect(marker3cluster8._iconNeedsUpdate).to.not.be.ok();
        expect(marker3cluster3._iconNeedsUpdate).to.not.be.ok();

    });

    it('accepts an L.MarkerCluster', function () {

        init3clusterBranches();

        // Then request clusters refresh.
        // No need to actually modify the markers.
        group.refreshClusters(marker1cluster8);
        // Clusters of marker3, 4, 5 and 6 shall not be flagged.

        // Now check that non-visible clusters are flagged as "dirty".
        expect(marker1cluster8._iconNeedsUpdate).to.be.ok();
        expect(marker1cluster3._iconNeedsUpdate).to.be.ok();

        // Clusters of marker3 and 4 shall not be flagged.
        expect(marker3cluster8._iconNeedsUpdate).to.not.be.ok();
        expect(marker3cluster3._iconNeedsUpdate).to.not.be.ok();

        expect(marker5cluster8._iconNeedsUpdate).to.not.be.ok();
        expect(marker5cluster3._iconNeedsUpdate).to.not.be.ok();

    });
});
